package pkg.andru.widget;

import pkg.andru.widget.BitmapAdapter.IBitmapViewHolder;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListView;

/**
 * bitmap custom adpater, base on {@link BetterAdapter}</p>
 * <p>
 * </p>
 * 
 * @author idiottiger
 * 
 * @param <T>
 *            the adpater item
 * @param <E>
 *            the view holder
 * @param <V>
 *            the view tag binded to view
 */

public abstract class BitmapAdapter<T, E extends IBitmapViewHolder, V> extends BetterAdapter<T, E> implements OnScrollListener {

    static final int MSG_NOTIFY_DATA_CHANGED_WITH_TAG = 1 << 12;

    private AbsListView mListView;

    private volatile int viewStartPos;
    private volatile int viewEndPos;

    public BitmapAdapter(Context context) {
        super(context);
    }

    public BitmapAdapter(Context context, int itemLayout) {
        super(context, itemLayout);
    }

    /**
     * bind the adpater to listview, use this method to instead of
     * {@link AbsListView#setAdapter(android.widget.ListAdapter)}, and also
     * {@link AbsListView#setOnScrollListener(OnScrollListener)} to this
     * 
     * @param view
     */
    public void bindToListview(AbsListView view) {
        mListView = view;
        mListView.setOnScrollListener(this);
        mListView.setAdapter(this);
        mListView.setScrollingCacheEnabled(false);
    }

    /**
     * unbind current adpater to listview, if dont need adpater, use this unbind
     */
    public void unBindToListview() {
        mListView.setOnScrollListener(null);
        mListView.setAdapter(null);
        mListView = null;
    }

    /**
     * check the tag is in current screen or not
     * 
     * @param tag
     * @return
     */
    public boolean isInVisualRange(V tag) {
        boolean result = false;
        // get the visual range
        final int startPos = getCurrentFirstVisiblePosition();
        final int endPos = getCurrentLastVisiblePosition();

        // if dont set OnScrollListener to shis adpter, will also return true
        if (startPos == -1 && endPos == -1) {
            result = true;
            return result;
        }
        if (endPos >= startPos && startPos >= 0 && tag != null) {
            for (int i = startPos; i <= endPos; i++) {
                V atag = getTagInPosition(i);
                if (tag.equals(atag)) {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }

    /**
     * get the tag in position
     * 
     * @param pos
     * @return
     */
    public abstract V getTagInPosition(int pos);

    /**
     * bind the tag to view
     * 
     * @param holder
     * @param pos
     */
    protected void bindTagToViewInPosition(E holder, int pos) {
        ImageView iv = holder.getTagBindView();
        if (iv != null) {
            iv.setTag(getTagInPosition(pos));
        }
    }

    /**
     * notify data changed use given tag and bitmap.
     * <p>
     * <b>tip for this method: 1.this method like
     * {@link #notifyDataSetChanged()}, dont care run in ui thread or
     * not;</br>2. before use this need invoke
     * {@link #bindToListview(AbsListView)} to bind the listview;</br>3.will
     * check the tag is {@link #isInVisualRange(tag)}, if not, will dont notify
     * data changed, suggestion before notify, need cache your bitmap</b>
     * </p>
     * 
     * @param tag
     * @param bitmap
     */
    public void notifyDataSetChanged(V tag, Bitmap bitmap) {
        if (mListView != null && isInVisualRange(tag)) {
            if (isRunInUiThread()) {
                notifyChanged(tag, bitmap);
            } else {
                Item item = new Item(tag, bitmap);
                Message msg = Message.obtain(mHandler, MSG_NOTIFY_DATA_CHANGED_WITH_TAG, item);
                msg.sendToTarget();
            }
        }
    }

    private void notifyChanged(V tag, Bitmap bitmap) {
        View view = mListView.findViewWithTag(tag);
        if (view != null) {
            ImageView iv = (ImageView) view;
            iv.setImageBitmap(bitmap);
        }
    }

    /**
     * add tag bind, if you override this method, dont forget invoke <b>
     * super.updateView(int position,E holder)</b>
     */
    protected void updateView(int position, E holder) {
        bindTagToViewInPosition(holder, position);
    }

    /**
     * get current first visible item position.</br><b>but before use this need
     * {@link ListView#setOnScrollListener(OnScrollListener)} to this adapter.
     * 
     * @return
     */
    public final int getCurrentFirstVisiblePosition() {
        return viewStartPos;
    }

    /**
     * get current last visible item position.</br><b>but before use this need
     * {@link ListView#setOnScrollListener(OnScrollListener)} to this adapter.
     * 
     * @return
     */
    public final int getCurrentLastVisiblePosition() {
        return viewEndPos;
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (visibleItemCount != 0) {
            viewStartPos = firstVisibleItem;
            viewEndPos = firstVisibleItem + visibleItemCount - 1;
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    @SuppressWarnings("unchecked")
    @Override
    void pendProcessMessage(Handler handler, Message message) {
        final int what = message.what;
        if (what == MSG_NOTIFY_DATA_CHANGED_WITH_TAG) {
            Item item = (Item) (message.obj);
            notifyChanged(item.tag, item.map);
        }
    }

    class Item {
        Bitmap map;
        V tag;

        public Item(V v, Bitmap b) {
            tag = v;
            map = b;
        }
    }

    /**
     * 
     * @author idiottiger
     * 
     */
    public static interface IBitmapViewHolder {
        /**
         * return the
         * 
         * @return
         */
        public ImageView getTagBindView();
    }

}
